/*!	\file module.c
 *
 *	в этом файле хранятся основные функции работы с суперблоком,
 *	функции инициализации файловой системы и самого модуля.
 *
 */	

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/version.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/vfs.h>
#include <linux/sys.h>
#include <linux/types.h>
#include <linux/unistd.h>
#include <linux/pagemap.h>
#include <linux/dcache.h>
#include <linux/smp_lock.h>
#include <linux/buffer_head.h>

#include "myfs.h"
#include "myfs_inode.h"

#define DRIVER_AUTHOR "Sergey Reutsky"		/*!< автор модуля */
#define DRIVER_DESC   "file system driver"	/*!< краткое описание драйвера */


static int myfs_statfs(struct dentry *dentry, struct kstatfs *buf);

/*!
	указатель на кэш и-нодов
*/
static struct kmem_cache *myfs_inode_cachep;

/*!
	функция выделения памяти для нового и-узла
*/
static struct inode *myfs_alloc_inode(struct super_block *sb)
{
	struct myfs_inode_info *msi;
	msi = (struct myfs_inode_info *)kmem_cache_alloc(myfs_inode_cachep, GFP_KERNEL);
	if (!msi)
		return NULL;
	msi->vfs_inode.i_version = 1;
	return &msi->vfs_inode;
}

/*!
	функция используется для записи суперблока файловой системы на диск
*/
static void myfs_sync_super(struct super_block *sb, struct myfs_super_block *myfs_sb)
{
	mark_buffer_dirty(MYFS_SB(sb)->s_bh);
	sync_dirty_buffer(MYFS_SB(sb)->s_bh);
	sb->s_dirt = 0;
}

/*!
	функция освобождает память из-под структуры super_block
*/
static void myfs_put_super(struct super_block *sb)
{
	struct myfs_sb_info *msi = MYFS_SB(sb);
	struct myfs_super_block *myfs_sb = msi->s_mfs;
	myfs_sync_super(sb, myfs_sb);
	brelse(msi->inode_bitmap);
	brelse(msi->s_bh);
	sb->s_fs_info = NULL;
	kfree(msi);
}

/*!
	функция обновляет суперблок файловой системы
*/
void myfs_write_super(struct super_block *sb)
{
	struct myfs_sb_info *msi = MYFS_SB(sb);
	struct myfs_super_block *myfs_sb = msi->s_mfs;
	lock_kernel();
	myfs_sync_super(sb, myfs_sb);
	sb->s_dirt = 0;
	unlock_kernel();
}

/*!
	функция удаления и-узла из кэша
*/
static void myfs_destroy_inode(struct inode *inode)
{
	kmem_cache_free(myfs_inode_cachep, MYFS_I(inode));
	return;
}

/*!
	в данной структуре хранятся ссылки на фукции
	работы с суперблоком файловой системы
*/
static const struct super_operations myfs_sops = {
	.alloc_inode   = myfs_alloc_inode,
	.write_inode   = myfs_write_inode,
	.delete_inode  = myfs_delete_inode,
	.destroy_inode = myfs_destroy_inode,
	.put_super     = myfs_put_super,
	.write_super   = myfs_write_super,
 	.statfs	       = myfs_statfs,
};

static int myfs_statfs(struct dentry *dentry, struct kstatfs *buf)
{
	struct super_block *sb = dentry->d_sb;
	struct myfs_sb_info *msbi = MYFS_SB(sb);
	u64 id = huge_encode_dev(sb->s_bdev->bd_dev);
	buf->f_blocks = msbi->s_inodes_count + 1;
	buf->f_type = sb->s_magic;
	buf->f_bsize = sb->s_blocksize;
	buf->f_bfree = msbi->s_free_inodes_count;
	buf->f_bavail = buf->f_bfree;
	buf->f_ffree = msbi->s_free_inodes_count;
	buf->f_namelen = 12;
	buf->f_fsid.val[0] = (u32)id;
	buf->f_fsid.val[1] = (u32)(id >> 32);

	return 0;
}
	
/*static struct dentry *myfs_mount(struct file_system_type *fs_type,
	int flags, const char *dev_name, void *data)
{
	return mount_bdev(fs_type, flags, dev_name, data, myfs_fill_super);
}*/

/*!
	функция используется для инициализации суперблока файловой системы
*/
static int myfs_fill_super(struct super_block *sb, void *data, int silent)
{
	unsigned int blocksize ;

	struct inode *root = NULL;
	struct dentry *root_dentry = NULL;
	struct buffer_head *bh = NULL;
	struct myfs_sb_info *msi = NULL; 
	struct myfs_super_block *myfs_sb = NULL;

	msi = kzalloc(sizeof(*msi), GFP_KERNEL);
	if(!msi)
		return -ENOMEM;
	sb->s_fs_info = msi;

	blocksize = sb_min_blocksize(sb, MYFS_DFLT_BLOCKSIZE);
	printk(KERN_ALERT "myfs: block size is : %u.\n", blocksize);

	if(!(bh = sb_bread(sb, MYFS_SUPER_BLOCK)))
	{
		printk(KERN_ALERT "myfs: unable to read superblock.\n");
		goto ret_err;
	}

	myfs_sb = (struct myfs_super_block *)((char *)(bh->b_data));

	msi->s_mfs = myfs_sb;
	msi->s_bh = bh;
	msi->s_free_inodes_count = myfs_sb->s_free_inodes_count;
	msi->s_inodes_count = myfs_sb->s_inodes_count;
	msi->s_first_nonmeta_inode = myfs_sb->s_first_nonmeta_inode;

	sb->s_magic = le32_to_cpu(myfs_sb->s_magic);
	sb->s_blocksize = MYFS_DFLT_BLOCKSIZE;
	sb->s_blocksize_bits = PAGE_CACHE_SHIFT;
	
	//kdev_t dev = sb->s_dev;
	sb->s_op = &myfs_sops;
	//sb->s_type = &myfs_type;

	//root = myfs_make_inode(sb, S_IFDIR | 0755);
	root = myfs_iget(sb, MYFS_ROOT_INODE(myfs_sb));
	if(!root)
	{
		printk(KERN_ALERT "myfs: reading root inode failed.\n");
		goto ret_err;
	}
	//root->i_op = &simple_dir_inode_operations;
	//root->i_fop = &simple_dir_operations;

	/* создание корневой директории */
	if(!(root_dentry = d_alloc_root(root)))
	{
		goto ret_err;
	}
	sb->s_root = root_dentry;

	return 0;  /* всё прошло нормально */

	/* если что-то пошло не так,
	 * освобождаем ресурсы и выходим
	 */
	ret_err:
		if(root)
			iput(root);
		if(bh)
			brelse(bh);
		sb->s_fs_info = NULL;
		kfree(msi);

		return -EINVAL;
}

/*!
	функция используется для считывания суперблока файловой системы с диска
*/
static int myfs_get_sb(struct file_system_type *fs_type,
	int flags, const char *dev_name, void *data, struct vfsmount *mnt)
{
	return get_sb_bdev(fs_type, flags, dev_name, data, myfs_fill_super, mnt);
}

/*!
	функция используется для освобождения суперблока файловой системы
*/
static void myfs_kill_sb(struct super_block *sb)
{
	generic_shutdown_super(sb);
}

/*!
 * структура file_system_type является глобальным "определителем"
 * файловой системы. Она содержит имя файловой системы, ссылки на
 * функции получения и уничтожения суперблока
 */
static struct file_system_type myfs_type = {
	.owner		 = THIS_MODULE,
	.name		 = "myfs",
	.get_sb		 = myfs_get_sb,
	//.mount	 = myfs_mount,
	.kill_sb 	 = myfs_kill_sb,
	.fs_flags	 = FS_REQUIRES_DEV, /* файловая система должна быть размещена на физическом носителе */
};

//static DECLARE_FSTYPE_DEV(myfs_type, "myfs", myfs_read_super);

/*!
	функция используется при инициализации кэша и-узлов
*/
static void init_once(void *object)
{
	struct myfs_inode_info * msi = (struct myfs_inode_info *)object;
	inode_init_once(&msi->vfs_inode);
	return;
}

/*!
	функция создания кэша для и-узлов
*/
static int init_inodecache(void)
{
	myfs_inode_cachep = kmem_cache_create("myfs_inode_cache",
						sizeof(struct myfs_inode_info),
						0, (SLAB_RECLAIM_ACCOUNT|SLAB_MEM_SPREAD),
						init_once);
	if(!myfs_inode_cachep)
		return -ENOMEM;
	return 0;

}

/*!
	функция удаления кэша для и-узлов
*/
static void destroy_inodecache(void)
{
	kmem_cache_destroy(myfs_inode_cachep);
}
	
/*! 	
	функция используется для  инициализации модуля, регистрации файловой системы
*/
static int __init init_start(void)
{
        printk(KERN_ALERT "In the kernel\n");
	int err;

	err = init_inodecache();
	if(err < 0)
	{
		goto out;
	}

	err = register_filesystem(&myfs_type);
	if(err < 0)
	{
		goto fail_reg;
	}

	printk(KERN_ALERT "MYFS is registered\n");
	return 0;	// файловая система успешно зарегистрирована

	fail_reg:
		destroy_inodecache();
		unregister_filesystem(&myfs_type);
	out: 		
		printk(KERN_ALERT "MYFS registration is failed\n");
		return err; // ошибка регистрации файловой системы
}

/*!
	функция используется для освобождения ресурсов, занимаемых модулем
*/
static void __exit cleanup_exit(void)
{
        printk(KERN_ALERT "Out the kernel\n");
	destroy_inodecache();
	unregister_filesystem(&myfs_type);
	printk(KERN_ALERT "MYFS is unregistered\n");
}

module_init(init_start);	 /*!< инициализация модуля */
module_exit(cleanup_exit);	 /*!< очистка модуля */

MODULE_LICENSE("GPL");		 /*!< лицензия */

MODULE_AUTHOR(DRIVER_AUTHOR);    /*!< автор модуля */
MODULE_DESCRIPTION(DRIVER_DESC); /*!< назначение модуля */
